#include <windows.h>
#include <string.h>
//#include <lapi.h>
#include <stdio.h>
#include "cpnmouse_mylapi.h"
#include "cpnmousewrapper.h"

#define eSuspend 1
#define eUnSuspend 2
#define eCleanup 3
#define MAX_BUTTONS 5

typedef struct MouseData {
	int dx, dy;
	int button_event;
	int suspended;
	unsigned char* buttons;
} MouseData;

MouseData *micedata = NULL;
int nummice = -1;

HANDLE child = NULL;
volatile int mousecount;
DWORD dwThreadId;
HANDLE datamutex = NULL;
HANDLE mutex = NULL;
HANDLE event = NULL;
HANDLE reply = NULL;

volatile int call, parameter;

void ClearButtons(unsigned char* buttons) {
	int i;
	for (i=0; i<MAX_BUTTONS; i++)
		buttons[i] = FALSE;
}

void DecodeButtonEvent(int button_event, unsigned char* buttons) {
//printf("%i %i %i  ", buttons, buttons  & 1, buttons & 2);
	int i, mask=1;
	for (i=0; i<MAX_BUTTONS; i++) {
		if ((button_event & mask))
			buttons[i] = TRUE;
		if ((button_event & mask*2))
			buttons[i] = FALSE;
		mask *= 4;
	}
}

void mouse_callback(int number, signed int dx, signed int dy, unsigned int button_event, int suspended) {

//	while (WaitForSingleObjectEx(datamutex, INFINITE, TRUE) != WAIT_OBJECT_0) ;

	/* A CALLBACK TO A JAVA METHOD CAN BE MADE HERE */

//printf("Received mouse=%i dx=%i dy=%i buttons=%i suspended=%i\n", number, dx, dy, buttons, suspended);
//printf("Received mouse=%s \n", mice[number].devicename);

	if ((number <= nummice) && (number > 0) && !micedata[number - 1].suspended) {
		micedata[number - 1].dx += dx;
		micedata[number - 1].dy += dy;
		micedata[number - 1].button_event = button_event;
		DecodeButtonEvent(button_event, micedata[number - 1].buttons);
	}

	ReleaseMutex(datamutex);
}

DWORD WINAPI MainThread( LPVOID lpParam ) {
	int loop = TRUE;
	lRegisterCallback(mouse_callback);
	parameter = lGetMice(mousecount);
	SetEvent(reply);
	while (loop) {
		if (WaitForSingleObjectEx(event, INFINITE, TRUE) == WAIT_OBJECT_0) {
			switch (call) {
				case eSuspend:
					lSuspendMouse(parameter);
					break;
				case eUnSuspend:
					lUnSuspendMouse(parameter);
					break;
				case eCleanup:
					lUnGetAllMice();
					lUnRegisterCallback();
					loop = FALSE;
				default: /* do nothing */;
			}
			ResetEvent(event);
			SetEvent(reply);
		}
	}
	return 0;
}

// Not thread safe until after first call
int Initialize(int count) {
	int i;
	if (event == NULL) {
		event = CreateEvent(NULL, TRUE, FALSE, NULL);
		reply = CreateEvent(NULL, TRUE, FALSE, NULL);
	}
	if (datamutex == NULL) {
		datamutex = CreateMutex(NULL, TRUE, NULL);
	}
	if (mutex == NULL) {
		mutex = CreateMutex(NULL, TRUE, NULL);
	}
	if (child == NULL) {
		mousecount = count;
		child = CreateThread(NULL, 0, MainThread, NULL, 0, &dwThreadId);
	}
	if (child == NULL) {
		printf("CPNMouse: Could not create mouse thread!\n");
		ReleaseMutex(mutex);
		return 0;
	}
	
	SetThreadPriority(child, 5);

	while (WaitForSingleObjectEx(reply, INFINITE, TRUE) != WAIT_OBJECT_0) ;
	nummice = count = parameter;
	if (micedata != NULL) free(micedata);
	micedata = malloc(sizeof(MouseData) * nummice);
	for (i = 0; i < nummice; ++i) {
		micedata[i].dx = micedata[i].dy = micedata[i].button_event = micedata[i].suspended = 0;
		micedata[i].buttons = malloc(sizeof(unsigned char) * MAX_BUTTONS);
		ClearButtons(micedata[i].buttons);
	}
	ReleaseMutex(datamutex);
	ResetEvent(reply);
	ReleaseMutex(mutex);
	return count;
}

void SuspendMouse(int number) {
	if (mutex == NULL) return;
	while (WaitForSingleObjectEx(mutex, INFINITE, TRUE) != WAIT_OBJECT_0) ;
	if ((child != NULL) && (number <= nummice) && (number > 0)) {
		micedata[number - 1].suspended = 1;
		parameter = number;
		call = eSuspend;
		SetEvent(event);
		while (WaitForSingleObjectEx(reply, INFINITE, TRUE) != WAIT_OBJECT_0) ;
		ResetEvent(reply);
	}
	ReleaseMutex(mutex);
}

void UnSuspendMouse(int number) {
	if (mutex == NULL) return;
	while (WaitForSingleObjectEx(mutex, INFINITE, TRUE) != WAIT_OBJECT_0) ;
	if ((child != NULL) && (number <= nummice) && (number > 0)) {
		micedata[number - 1].suspended = 0;
		parameter = number;
		call = eUnSuspend;
		SetEvent(event);
		while (WaitForSingleObjectEx(reply, INFINITE, TRUE) != WAIT_OBJECT_0) ;
		ResetEvent(reply);
	}
	ReleaseMutex(mutex);
}

void Cleanup(void) {
	if (mutex == NULL) return;
	while (WaitForSingleObjectEx(mutex, INFINITE, TRUE) != WAIT_OBJECT_0) ;
	if (child != NULL) {
		call = eCleanup;
		SetEvent(event);
		while (WaitForSingleObjectEx(reply, INFINITE, TRUE) != WAIT_OBJECT_0) ;
		ResetEvent(reply);
		CloseHandle(child); child = NULL;
	}
	ReleaseMutex(mutex);
}

void GetDeltas(int number, int *pos) {
	if ((datamutex == NULL) || (pos == NULL)) return;
	while (WaitForSingleObjectEx(datamutex, INFINITE, TRUE) != WAIT_OBJECT_0) ;
	if ((number <= nummice) && (number > 0)) {
		pos[0] = micedata[number - 1].dx;
		pos[1] = micedata[number - 1].dy;
		micedata[number - 1].dx -= pos[0];
		micedata[number - 1].dy -= pos[1];
	}
	ReleaseMutex(datamutex);
}

int GetLastButtonEvent(int number) {
	int b=0;
	if ((datamutex == NULL)) return 0;
	while (WaitForSingleObjectEx(datamutex, INFINITE, TRUE) != WAIT_OBJECT_0) ;
	if ((number <= nummice) && (number > 0)) {
		b = micedata[number - 1].button_event;
		micedata[number - 1].button_event = 0;
	}
	ReleaseMutex(datamutex);
	return b;
}


void GetButtons(int number, unsigned char* buttons) {
	int i;
	if ((datamutex == NULL)) {
		ClearButtons(buttons);
		return;
	}
	while (WaitForSingleObjectEx(datamutex, INFINITE, TRUE) != WAIT_OBJECT_0) ;
	if ((number <= nummice) && (number > 0)) {
		for (i=0; i<MAX_BUTTONS; i++)
			buttons[i] = micedata[number - 1].buttons[i];
		micedata[number - 1].button_event = 0;

	} else
		ClearButtons(buttons);
	ReleaseMutex(datamutex);
}


int HasChanged(int number) {
	int res = 0;
	if ((datamutex == NULL)) return 0;
	while (WaitForSingleObjectEx(datamutex, INFINITE, TRUE) != WAIT_OBJECT_0) ;
	if ((number <= nummice) && (number > 0))
		res = (micedata[number - 1].button_event != 0 || micedata[number - 1].dx != 0 || micedata[number - 1].dy != 0);
	ReleaseMutex(datamutex);
	return res;
}


JNIEXPORT jboolean JNICALL Java_fr_emn_win32input_jcpnmouse_JCpnMouse_ninitCPN
  (JNIEnv *env, jobject obj) {
	int i;
	Initialize(0);
	// Disable all mice by default
	for (i=1; i<=nummice; i++)
		SuspendMouse(i);
	return (datamutex != NULL) && nummice > 0;
}

JNIEXPORT void JNICALL Java_fr_emn_win32input_jcpnmouse_JCpnMouse_nreleaseCPN
  (JNIEnv *env, jobject obj) {
  	Cleanup();
}

JNIEXPORT jint JNICALL Java_fr_emn_win32input_jcpnmouse_JCpnMouse_getNumberOfMice
  (JNIEnv *env, jobject obj) {
  	return nummice;
}

JNIEXPORT void JNICALL Java_fr_emn_win32input_jcpnmouse_JCpnMouse_ngrabMouse
  (JNIEnv *env, jobject obj, jint mouse) {
	UnSuspendMouse(mouse);
}

JNIEXPORT void JNICALL Java_fr_emn_win32input_jcpnmouse_JCpnMouse_nungrabMouse
  (JNIEnv *env, jobject obj, jint mouse) {
	SuspendMouse(mouse);
}


JNIEXPORT jboolean JNICALL Java_fr_emn_win32input_jcpnmouse_JCpnMouse_hasChanged
  (JNIEnv *env, jobject obj, jint mouse) {
	return HasChanged(mouse);
}

JNIEXPORT void JNICALL Java_fr_emn_win32input_jcpnmouse_JCpnMouse_getDeltas
  (JNIEnv *env, jobject obj, jint mouse, jintArray axis) {
  	int* axis_c;
	axis_c = (*env)->GetIntArrayElements(env, axis, 0);
	GetDeltas(mouse, axis_c);
	(*env)->ReleaseIntArrayElements(env, axis, axis_c, 0);
}

JNIEXPORT void JNICALL Java_fr_emn_win32input_jcpnmouse_JCpnMouse_getButtons
  (JNIEnv *env, jobject obj, jint mouse, jbooleanArray buttons) {
  	unsigned char* buttons_c;
	buttons_c = (*env)->GetBooleanArrayElements(env, buttons, 0);
	GetButtons(mouse, buttons_c);
	(*env)->ReleaseBooleanArrayElements(env, buttons, buttons_c, 0);
}

JNIEXPORT jint JNICALL Java_fr_emn_win32input_jcpnmouse_JCpnMouse_getLastButtonEvent
  (JNIEnv *env, jobject obj, jint mouse) {
  	return GetLastButtonEvent(mouse);
}

JNIEXPORT jstring JNICALL Java_fr_emn_win32input_jcpnmouse_JCpnMouse_getDevicePath
  (JNIEnv *env, jobject obj, jint mouse) {
	jstring js = (*env)->NewStringUTF(env, lGetDevicePath(mouse+1));
	return js;
}

JNIEXPORT jstring JNICALL Java_fr_emn_win32input_jcpnmouse_JCpnMouse_getDriverKey
  (JNIEnv *env, jobject obj, jint mouse) {
	jstring js = (*env)->NewStringUTF(env, lGetDriverKey(mouse+1));
	return js;
}

JNIEXPORT jstring JNICALL Java_fr_emn_win32input_jcpnmouse_JCpnMouse_getHardwareId
  (JNIEnv *env, jobject obj, jint mouse) {
	jstring js = (*env)->NewStringUTF(env, lGetHardwareId(mouse+1));
	return js;
}